上一篇我們介紹了一個因 Activity 被 Singleton 參考所發生的 Memory leak 。
object Class1 {
lateinit var context: Context
}
class MainActivity : AppCompatActivity() {
private val binding by lazy { ActivityMain2Binding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
//傳入Activity context
Class1.initApp(this)
}
}
會造成 Memory leak 的原因在於用錯了 Context。Singleton 的生命週期跟整個 App 的生命週期是一樣的,所以應使用 Application context 而不是 Activity context。這一篇我們就來看一下 Activity 與 Application context 的差異在哪裡?
Context 是一個抽象類別,讓我們取得 Activity 與 Application 的環境資訊。例如開啟一個 Activity需要 Context,要顯示 Dialog、啟動 Service、發送 Broadcast、資料庫存取、Shared preferences 都需要 Context。
取得 Activity context:透過 View.getContext
或是在 Activity 直接用 this 取得。
val context = binding.imageView.context
val context = this
取得Application context:透過 getApplicationContext 取得。
this.applicationContext
例如在 Activity 要顯示 Dialog,AlertDialog.Builder(context)
裡要傳入的 Context 就必須是Activity content。
val alertDialog = AlertDialog.Builder(this)
alertDialog.setMessage("Message")
alertDialog.create().show()
如果傳入 applicationContext 是會閃退的
//傳入applicationContext會閃退
val alertDialog = AlertDialog.Builder(applicationContext)
alertDialog.setMessage("Message")
alertDialog.create().show()
這兩個最大的差別是 Activity Context 的生命週期是跟著 Activity,Application context 的生命週期是跟著Application。在應該傳入 Application context 的地方如果傳入了 Activity context 就會造成Memory leak。
回來修正剛剛的 Singleton 範例,我們試著來修正這個 Memory leak。
object Class1 {
lateinit var context: Context
}
Class1 既然生命週期與 App 是一樣的,所以需要的是 Application Context,我們就想辦法讓context 只能被設定為 Application context。
步驟:
context
改為 priviate
,使其無法直接被設定。initApp
function 傳入 context
。context.applicationContext
一律轉為 Application context。即使傳入了 Activity,也會轉成 Application Context。object Class1 {
private lateinit var context: Context
//如果傳入了Activity context,也不會直接放在staic,而是取得ApplicationContext
fun initApp(context:Context){
context = context.applicationContext
}
}
class MainActivity : AppCompatActivity() {
private val binding by lazy { ActivityMain2Binding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
//傳入Activity context
Class1.initApp(this)
}
}
這樣就修改完成了,確保拿到的一定會是 Application context。最後,誤用 Context 是最常發生的 Memory leak,一定要小心使用 Context。